/*
 * stegofiles.c
 *
 *  Created on: May 23, 2011
 *      Author: tommy
 */

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <openssl/evp.h>
#include "include/structs.h"
#include "include/steganography.h"
#include "include/wav.h"
#include <arpa/inet.h>

#define MAX_EXTENSION_SIZE 20
typedef enum {LSBE_ENCODEBYTE_1 = 0xfe, LSBE_ENCODEBYTE_2}	LSBE_ENCODEBYTES;

void embedDataStandard(int datasize, int offset, BYTE * carrierData, BYTE * data, StegoMode mode);
void embedDataEnhanced(int datasize, wavT * carrier, BYTE * data, StegoMode mode);
char * decodeStandardExtension(wavT * carrier, StegoMode mode, int offset, int size);
char * decodeEnhanceExtension(wavT * carrier, int size);
BYTE * extractFileStandard(BYTE * dataToExtract, int offset, int outl, StegoMode mode);
BYTE * extractFileEnhanced(BYTE * dataToExtract, int size);

StegoFileT *
readStegoFile(const char * sfilename)
{
	StegoFileT * sfiledata;
	FILE * stegoFile;
	char * extension;
	int fsize;

	if ( (stegoFile = fopen(sfilename, "r")) == NULL)
	{
		return NULL;
	}

	if ( (sfiledata = calloc(1, sizeof(StegoFileT))) == NULL)
	{
		fclose(stegoFile);
		return NULL;
	}


	// Reads file size
	fseek(stegoFile, 0L, SEEK_END);
	fsize = ftell(stegoFile);
	fseek(stegoFile, 0L, SEEK_SET);

	if ( (sfiledata -> fileContents = calloc(1, fsize)) == NULL)
	{
		fprintf(stderr,"Out of memory when reading %s\n", sfilename);
		fclose(stegoFile);
		free(sfiledata);
		return NULL;
	}

	// assign filesize, reads in data from file and sets file extension
	sfiledata -> fileSize = fsize;
	fread(sfiledata -> fileContents, fsize,1,stegoFile);

	// checks for file read error
	if (ferror(stegoFile) > 0)
	{
		free(sfiledata);
		fclose(stegoFile);
		return NULL;
	}

	extension = strstr(sfilename, ".");

	if ((sfiledata -> extension = calloc(1, sizeof(char) * strlen(extension) + 1)) == NULL)
	{
		free(sfiledata);
		fclose(stegoFile);
		return NULL;
	}

	strncpy(sfiledata -> extension, extension, strlen(extension));

	fclose(stegoFile);
	return sfiledata;
}

int
writeStegoFile(const char * out, StegoFileT * sfile)
{
	FILE * stegoFile;
	char * finalname; // final name of file (out + extension)

	if ( (finalname = calloc(1, strlen(out) + strlen(sfile -> extension) + 1)) == NULL )
	{
		fprintf(stderr, "Error, out of memory writing file for extraction\n");
		return 0;
	}

	strcpy(finalname, out);
	strcat(finalname, sfile -> extension);

	if ( (stegoFile = fopen(finalname, "w+")) == NULL)
	{
		free(finalname);
		fprintf(stderr, "Error creating file %s\n", finalname);
		return 0;
	}

	fwrite(sfile -> fileContents, sfile -> fileSize, 1, stegoFile);

	if (ferror(stegoFile))
	{
		free(finalname);
		fclose(stegoFile);
		fprintf(stderr, "Error writing to file %s\n", finalname);
		return 0;
	}

	free(finalname);
	fclose(stegoFile);
	return 1;
}



int
hideFile(BYTE * data, int datasize, wavT * carrier, int (* steganograph) (wavT *, BYTE *, int))
{
	return (*steganograph)(carrier, data, datasize);
}

int
lsb1(wavT * carrier, BYTE * data, int datasize)
{
	DWORD wavSamples, wavSamplesNeeded;
	BYTE * carrierData;
	int offset, maxCapacity;

	offset = carrier -> fmt.wBitsPerSample / CHAR_BIT;
	carrierData = carrier -> data.soundData;
	wavSamples = carrier -> data.chunkSize / (carrier -> fmt.wBitsPerSample / CHAR_BIT);
	wavSamplesNeeded = datasize * CHAR_BIT;
	maxCapacity = ( (wavSamples / CHAR_BIT) * (carrier -> fmt.wBitsPerSample / CHAR_BIT) );

	if (wavSamples < wavSamplesNeeded) {
		fprintf(stderr, "lsb1: Error, carrier file not big enough to hold data, maximum capacity of carrier file is: %d bytes\n", maxCapacity);
		return 0;
	}

	// proceeds to steganograph data
	embedDataStandard(datasize, offset, carrierData, data, LSB1);

	return 1;
}

int
lsb4(wavT * carrier, BYTE * data, int datasize)
{
	DWORD wavSamples, wavSamplesNeeded;
	BYTE * carrierData;
	int offset, maxCapacity;

	offset = carrier -> fmt.wBitsPerSample / CHAR_BIT;
	carrierData = carrier -> data.soundData;
	wavSamples = carrier -> data.chunkSize / (carrier -> fmt.wBitsPerSample / CHAR_BIT);
	wavSamplesNeeded = datasize * 2;
	maxCapacity = ( (wavSamples / 2) * (carrier -> fmt.wBitsPerSample / CHAR_BIT) );

	if (wavSamples < wavSamplesNeeded) {
		fprintf(stderr, "lsb4: Error, carrier file not big enough to hold data, maximum capacity of carrier file is: %d bytes\n", maxCapacity);
		return 0;
	}

	// proceeds to steganograph data
	embedDataStandard(datasize, offset, carrierData, data, LSB4);

	return 1;
}

int
lsbe(wavT * carrier, BYTE * data, int datasize)
{
	BYTE * carrierData;

	if (carrier -> data.chunkSize < datasize)
	{
		fprintf(stderr, "Carrier file size is smaller than message to hide\n");
		return 0;
	}

	carrierData = carrier -> data.soundData;
	// proceeds to steganograph data
	embedDataEnhanced(datasize, carrier, data, LSBE);

	return 1;
}

void
embedDataStandard(int datasize, int offset, BYTE * carrierData, BYTE * data, StegoMode mode)
{
	int i, displace, bitsToCopy, bitMovement;
	BYTE dataaux, mask, clearMask;

	if (mode == LSB1 || mode == LSBE)
	{
		bitsToCopy = 1;
		mask = 0x01;
		clearMask = 0xFE;
		bitMovement = 7;
	}
	else
	{
		bitsToCopy = bitMovement = 4;
		mask = 0x0f;
		clearMask = 0xF0;
	}

	displace = (CHAR_BIT/bitsToCopy);

	for (i = 0 ; i < datasize * displace; i++)
	{

		// if displace equals 0, process next byte of data to be steganograph
		if (i % displace == 0)
		{
			dataaux = (*data);
			data++;
		}

		// lsb1: embeds 1 bit
		// lsb4: embeds 4 bits
		carrierData[0] &= clearMask; // clears 0th bit
		carrierData[0] |= ((dataaux >> bitMovement) & mask); // sets 0th bit
		dataaux <<= bitsToCopy; // next bit/s of data
		carrierData += offset; // moves to next sample
	}

	return;
}

void
embedDataEnhanced(int datasize, wavT * carrier, BYTE * data, StegoMode mode)
{
	int i, displace, bitsToCopy, bitMovement, bitsEncoded, bytesEncoded, encodedFinished;
	BYTE dataaux, mask, clearMask, * carrierData;

	bitsToCopy = 1;
	mask = 0x01;
	clearMask = 0xFE;
	bitMovement = CHAR_BIT - 1;
	bytesEncoded = bitsEncoded = encodedFinished = i = 0;
	carrierData = carrier -> data.soundData;

	displace = (CHAR_BIT/bitsToCopy);
	dataaux = (*data);


	while(!encodedFinished && i < carrier -> data.chunkSize)
	{
		if (carrierData[0] == LSBE_ENCODEBYTE_1 || carrierData[0] == LSBE_ENCODEBYTE_2)
		{
			carrierData[0] &= clearMask; // clears 0th bit
			carrierData[0] |= ((dataaux >> bitMovement) & mask); // sets 0th bit
			dataaux <<= bitsToCopy; // next bit/s of data
			bitsEncoded++;
		}

		// byte successfuly encoded? read next byte of data
		if (bitsEncoded == CHAR_BIT)
		{
			data++;
			dataaux = (*data);
			bytesEncoded++; // increments amount of bytes encoded from source
			bitsEncoded = 0; // restarts bit count

			if (bytesEncoded == datasize)
			{
				encodedFinished = 1;
			}

		}

		carrierData++; // moves to next sample
		i++;
	}

	if (bytesEncoded < datasize)
	{
		fprintf(stderr,"Carrier file can not hide message, max capacity is %d\n", bytesEncoded);
		return;
	}

	return;
}

BYTE *
getStegoFileSize(Params params, wavT * carrier, int offset, int * size)
{
	int i, samplesNeeded, mask, shifts, lsbeFinished, bitsDecoded;
	BYTE * data = carrier -> data.soundData;

	// inits size as 0
	(*size) = lsbeFinished = bitsDecoded = 0;

	// samplesNeeded: amount of samples to be queried to extract bits
	if (params.s == LSB1 || params.s == LSBE)
	{
		samplesNeeded = 32;
		shifts = 1;
		mask = 0x01;
	}
	else
	{
		samplesNeeded = 8;
		shifts = 4;
		mask = 0x0f;
	}
	// transfer bit/s to size
	if (params.s  != LSBE)
	{
		for (i = 0 ; i < samplesNeeded ; i++)
		{
			(*size) |= (*data) & mask;		// transfer bit/s to size

			if (i != samplesNeeded - 1)
			{
				(*size) <<= shifts;			// right-shift bits to allow next bits to be transfered
			}
			data += offset;
		}
	}
	else
	{
		i = 0;
		while(!lsbeFinished && i < carrier -> data.chunkSize)
		{
			if ((*data) == LSBE_ENCODEBYTE_1 || (*data) == LSBE_ENCODEBYTE_2)
			{
				(*size) |= (*data) & mask;		// transfer bit/s to size
				bitsDecoded++;

				if (bitsDecoded != samplesNeeded)
				{
					(*size) <<= shifts;
				}
				else
				{
					lsbeFinished = 1;
				}
			}
			i++;
			data++;
		}
	}
	return data;
}


BYTE *
extractFile(Params params, BYTE * dataToExtract, int offset, int outl)
{
	BYTE * chunk, dataaux;


	dataaux = 0;

	if (params.s == LSB1 || params.s == LSB4)
	{
		chunk = extractFileStandard(dataToExtract, offset, outl, params.s);
	}
	else
	{
		chunk = extractFileEnhanced(dataToExtract, outl);
	}
	return chunk;
}

BYTE *
extractFileStandard(BYTE * dataToExtract, int offset, int outl, StegoMode mode)
{
	BYTE * chunk, dataaux;
	int i, j, mask, displace, bitsToCopy;

	dataaux = 0;

	if ( (chunk = calloc(1, outl)) == NULL)
	{
		fprintf(stderr, "Out of memory when extracing data from carrier file\n");
		return NULL;
	}

	if (mode == LSB1)
	{
		bitsToCopy = 1;
		mask = 0x01;
	}
	else
	{
		bitsToCopy = 4;
		mask = 0x0f;
	}

	displace = (CHAR_BIT/bitsToCopy );

	for (i = 0, j = 0 ; i < outl * displace; i++)
	{
		dataaux |= dataToExtract[0] & mask;

		// avoids bit shifting and dataloss when on next i, data dump to chunk occurs, otherwise 1 o 4 bits (lsb1, lsb4) would be lost!
		if ((i + 1) % displace != 0)
		{
			dataaux <<= bitsToCopy;
		}
		else {
			chunk[j++] = dataaux;
			dataaux = 0;
		}
		dataToExtract += offset;
	}


	return chunk;
}

BYTE *
extractFileEnhanced(BYTE * dataToExtract, int size)
{
	int i, j, mask, displace, bitsToCopy, decodedBits, decodedBytes,decodingFinished;
	BYTE * chunk, dataaux;

	dataaux = decodingFinished = decodedBits = decodedBytes = i = j = 0;

	if ( (chunk = calloc(1, size)) == NULL)
	{
		fprintf(stderr, "Out of memory when extracing data from carrier file\n");
		return NULL;
	}

	bitsToCopy = 1;
	mask = 0x01;
	displace = (CHAR_BIT/bitsToCopy );

	while(!decodingFinished && i < size)
	{
		if (dataToExtract[0] == LSBE_ENCODEBYTE_1 || dataToExtract[0] == LSBE_ENCODEBYTE_2)
		{
			dataaux |= dataToExtract[0] & mask;
			decodedBits++;
			// avoids bit shifting and dataloss when on next i, data dump to chunk occurs, otherwise 1 o 4 bits (lsb1, lsb4) would be lost!
			if (decodedBits != CHAR_BIT)
			{
				dataaux <<= bitsToCopy;

			}
			else
			{
				chunk[j++] = dataaux;
				decodedBits = 0;
				decodedBytes++;
				dataaux = 0;
			}

			if (decodedBytes == size)
			{
				decodingFinished = 1;
			}

		}
		dataToExtract++;
	}

	return chunk;
}


char *
getStegoFileExtension(Params params, wavT * carrier, int offset, int outl)
{

	char * extension = NULL;

	// extracts file extension now!
	if (params.s != LSBE)
	{
		extension = decodeStandardExtension(carrier, params.s, offset, outl);
	}
	else
	{
		extension = decodeEnhanceExtension(carrier, outl);
	}

	return extension;
}

char *
decodeStandardExtension(wavT * carrier, StegoMode mode, int offset, int size)
{
	int i, k, extensionFinished, mask, bitsToCopy, displace;
	BYTE dataaux, * src;
	char * extension;

	i = k = extensionFinished = dataaux = 0;
	src = carrier -> data.soundData;

	if (mode == LSB1)
	{
		bitsToCopy = 1;
		mask = 0x01;
	}
	else
	{
		bitsToCopy = 4;
		mask = 0x0f;
	}

	displace = (CHAR_BIT / bitsToCopy);

	// moves to beggining of extension, needs to jump file-size indicator +
	// file-size
	src += (sizeof(int)*displace*offset + size * displace * offset);

	if ( (extension = calloc(1, MAX_EXTENSION_SIZE * sizeof(char))) == NULL)
	{
		fprintf(stderr, "Out of memory when allocating space for file extension\n");
		return NULL;
	}

	while(!extensionFinished)
	{
		dataaux |= src[0] & mask;

		// avoids bit shifting and dataloss when on next i, data dump to chunk occurs, otherwise 1 o 4 bits (lsb1, lsb4) would be lost!
		if ((i + 1) % displace != 0)
		{
			dataaux <<= bitsToCopy;
		}
		else
		{
			if (k == MAX_EXTENSION_SIZE)
			{
				fprintf(stderr, "Error, max extension reached reading file extension\n");
				free(extension);
				return NULL;
			}
			extension[k++] = dataaux;

			if (dataaux == 0)
			{
				extensionFinished = 1;
			}
			dataaux = 0;
		}
		src += offset;
		i++;
	}

	return extension;
}

char *
decodeEnhanceExtension(wavT * carrier, int size)
{
	char * extension;
	int i, k, extensionFinished, mask, bitsToCopy, displace, lsbeBytes, bitsEncoded;
	int qtyBytesBeforeExtension;
	BYTE dataaux, * src;

	if ( (extension = calloc(1, carrier -> data.chunkSize * sizeof(char))) == NULL)
	{
		fprintf(stderr, "Out of memory when allocating space for file extension\n");
		return NULL;
	}

	i = k = extensionFinished = dataaux = lsbeBytes = bitsEncoded = 0;
	bitsToCopy = 1;
	mask = 0x01;
	displace = (CHAR_BIT / bitsToCopy);
	src = carrier -> data.soundData;
	qtyBytesBeforeExtension = sizeof(int)*displace + size*displace;

	while(lsbeBytes < qtyBytesBeforeExtension && i < carrier -> data.chunkSize)
	{
		if (*src == LSBE_ENCODEBYTE_1 || *src == LSBE_ENCODEBYTE_2)
		{
			lsbeBytes++;
		}
		i++;
		src++;
	}


	while(!extensionFinished && i < carrier -> data.chunkSize)
	{
		if (src[0] == LSBE_ENCODEBYTE_1 || src[0] == LSBE_ENCODEBYTE_2)
		{
			// fsize indicator and file contents already left behind

			dataaux |= src[0] & mask;
			bitsEncoded++;


			// byte ready to be dumped
			if (bitsEncoded == CHAR_BIT)
			{
				bitsEncoded = 0;
				if (k == MAX_EXTENSION_SIZE)
				{
					fprintf(stderr, "Error, max extension reached reading file extension\n");
					free(extension);
					return NULL;
				}

				extension[k++] = dataaux;

				if (dataaux == 0)
				{
					extensionFinished = 1;
				}

				dataaux = 0;
			}
			else
			{
				dataaux <<= bitsToCopy;
			}

		}
		src++; // read next byte
		i++;
	}

	if (i >= carrier -> data.chunkSize)
	{
		fprintf(stderr, "Carrier file processed and no file extension is present");
		free(extension);
		return NULL;
	}

	return extension;
}
void
freeStegoFile(StegoFileT * sfile)
{
	free(sfile -> extension);
	free(sfile -> fileContents);
	free(sfile);
	return;
}
